import logging
import os
import errno 
import time
import inspect
import socket
import uuid
import threading
import threadpool

from rpyc import Service
from rpyc.utils.server import ThreadedServer

from daemon import Daemon
from utils import Exp, _dmsg, _dwarn, _derror, \
    etchosts_update, cpu_usage, _exec_shell, _exec_pipe, \
    mutil_exec, _get_value, _human_unreadable, _human_readable, \
    _exec_pipe1, _exec_remote1, _lock_file, _exec_system, _put_remote

def __line__ ():
    caller = inspect.stack()[2]
    return int (caller[2])

glo_foreground = None
glo_conf = None
glo_home = None
glo_shm = '/dev/shm/lich4/remotecopy'
glo_prefix = 'lich_system_remotecopy'

class RpycSrvImpl(Service):
    def _dinfo(self, msg):
        if glo_foreground:
            print "%s/%d %s %s:%s INFO: %s" %(time.strftime('%Y-%m-%d %H:%M:%S'),
                    time.time(), os.getpid(), __file__, __line__(), msg)
        else:
            logging.info(msg)

    def _derror(self, msg):
        if glo_foreground:
            print "%s/%d %s %s:%s ERROR: %s" %(time.strftime('%Y-%m-%d %H:%M:%S'),
                    time.time(), os.getpid(), __file__, __line__(), msg)
        else:
            logging.error(msg)

    def __exec_check(self):
        if 'stor' in glo_conf:
            if glo_conf['stor']['host'].startswith('127.'):
                return False
            else:
                return True
        else:
            return False

    def __exec_cmd(self, cmd):
        if self.__exec_check():
            user = glo_conf['stor']['user']
            password = glo_conf['stor']['pass']
            host = glo_conf['stor']['host']
            return _exec_remote1(host, cmd, user, password)
        else:
            return _exec_pipe1(cmd.split(), 0, True, 30)

    def __exec_put(self, cmd, local, remote):
        user = glo_conf['stor']['user']
        password = glo_conf['stor']['pass']
        host = glo_conf['stor']['host']
        _put_remote(host, local, remote, user, password)

        (out_msg, err_msg) = self.__exec_cmd(cmd)

        cmd = "rm -rf %s" % remote
        (out_msg, err_msg) = self.__exec_cmd(cmd)

    def __file_write(self, host, name, file, context):
        stat_path = os.path.join(glo_home, "stat/%s/%s"% (host, name))
        if not os.path.exists(stat_path):
            os.makedirs(stat_path)

        fd = open("%s/%s" % (stat_path, file), 'wb')
        fd.write(str(context))
        fd.close()

    def __file_read(self, host, name, file):
        stat_path = os.path.join(glo_home, "stat/%s/%s"% (host, name))
        if not os.path.exists(stat_path):
            os.makedirs(stat_path)

        try:
            fd = open("%s/%s" % (stat_path, file), 'rb')
            context = fd.read()
            fd.close()

            return context
        except:
            return None

    def __volume_exists(self, volume):
        try:
            cmd = "lich.inspect --stat %s" % volume
            (out_msg, err_msg) = self.__exec_cmd(cmd)
            return True
        except Exp, e:
            if e.errno == errno.ENOENT:
                return False

            raise

    def __volume_create(self, volume):
        self._dinfo("create volume :%s" %(volume))
        try:
            cmd = "lichfs --mkdir %s -p" % os.path.dirname(volume)
            (out_msg, err_msg) = self.__exec_cmd(cmd)
        except Exp, e:
            if e.errno == errno.EEXIST:
                pass
            else:
                raise
        try:
            cmd = "lichfs --touch %s" % volume
            (out_msg, err_msg) = self.__exec_cmd(cmd)
        except Exp, e:
            if e.errno == errno.EEXIST:
                pass
            else:
                raise

        self.__volume_exists(volume)

    def __volume_set_status(self, host, name, status):
        return self.__file_write(host, name, 'status', status)

    def __volume_get_status(self, host, name):
        return self.__file_read(host, name, 'status')

    def __volume_set_slast(self, host, name, slast):
        return self.__file_write(host, name, 'slast', slast)

    def __volume_get_slast(self, host, name):
        return self.__file_read(host, name, 'slast')

    def __volume_set_chknum(self, host, name, chknum):
        return self.__file_write(host, name, 'chknum', chknum)

    def __volume_get_chknum(self, host, name):
        return self.__file_read(host, name, 'chknum')

    def __volume_set_volume(self, host, name, volume):
        return self.__file_write(host, name, 'volume', volume)

    def __volume_get_volume(self, host, name):
        return self.__file_read(host, name, 'volume')

    def __volume_set_slist(self, host, name, slast):
        return self.__file_write(host, name, 'slist', slast)

    def __volume_get_slist(self, host, name):
        slist = self.__file_read(host, name, 'slist')
        return eval(slist) if slist is not None else []

    def __chunk_set_status(self, host, name, i, status):
        return self.__file_write(host, name, str(i), status)

    def __chunk_get_status(self, host, name, i):
        return self.__file_read(host, name, str(i))

    def __chunk_write(self, host, name, vol_name, i, context):
        chunk = os.path.join(glo_shm, "%s/%s/%s"%(host, name, i))
        if not os.path.exists(os.path.dirname(chunk)):
            os.makedirs(os.path.dirname(chunk))

        fd = open(chunk, 'wb')
        fd.write(context)
        fd.close()

        if self.__exec_check():
            newuuid = str(uuid.uuid1()).replace('-', '')
            tmpfile = '/tmp/temp.%s' % newuuid
            try:
                cmd = "lichfs --apply :%s %s" % (tmpfile, vol_name)
                self.__exec_put(cmd, chunk, tmpfile)
            except:
                cmd = "rm -rf %s" % tmpfile
                (out_msg, err_msg) = self.__exec_cmd(cmd)
                raise
        else:
            cmd = "lichfs --apply :%s %s" % (chunk, vol_name)
            (out_msg, err_msg) = self.__exec_cmd(cmd)

        os.unlink(chunk)

    def __snapshot_create(self, vol_name):
        snap_name = "%s.%d" %(glo_prefix, time.time())
        self._dinfo("create snapshot:%s@%s"%(vol_name, snap_name))
        cmd = "lich.snapshot --create %s@%s --force -p 1" %(vol_name, snap_name)
        (out_msg, err_msg) = self.__exec_cmd(cmd)
        return snap_name

    def __snapshot_get_list(self, vol_name):
        list = []
        cmd = "lich.snapshot --list %s" % vol_name
        (out_msg, err_msg) = self.__exec_cmd(cmd)
        if out_msg:
            return [x for x in out_msg.split() if x.startswith("%s."%(glo_prefix))]

        return []

    def __snapshot_remove(self, vol_name, snap_name):
        self._dinfo("remove snapshot:%s@%s"%(vol_name, snap_name))
        cmd = "lich.snapshot --remove %s@%s --force" %(vol_name, snap_name)
        (out_msg, err_msg) = self.__exec_cmd(cmd)

    def __volume_snapshot_prep(self, host, name, vol_name):
        slist_cmd =  self.__snapshot_get_list(vol_name)
        slist_local = self.__volume_get_slist(host, name)
        if len(slist_cmd) == 0:
            snap_name = self.__snapshot_create(vol_name)
            slist_cmd.append(snap_name)
            self.__volume_set_slist(host, name, slist_cmd)
        elif len(slist_cmd) == 1:
            if slist_cmd != slist_local:
                raise Exp(errno.EPERM, "snapshot get by cmd is %s but local record snapshot is %s"
                        %(slist_cmd, slist_local))
        else:
            raise Exp(errno.EPERM, "snapshot get by cmd is %s" %slist_cmd)

        return slist_cmd

    def __volume_snapshot_finish(self, host, name, vol_name):
        slist_local = self.__volume_get_slist(host, name)
        return self.__snapshot_remove(vol_name, slist_local[0])

    def __volume_reset(self, host, name):
        self._dinfo("volume %s %s reset" %(host, name))
        stat_path = os.path.join(glo_home, "stat/%s/%s"% (host, name))
        if not os.path.exists(stat_path):
            os.makedirs(stat_path)
        [os.unlink(os.path.join(stat_path, x)) for x in os.listdir(stat_path)]

    def __chunk_reset(self, host, name):
        self._dinfo("volume %s %s chunk reset" %(host, name))
        stat_path = os.path.join(glo_home, "stat/%s/%s"% (host, name))
        if not os.path.exists(stat_path):
            os.makedirs(stat_path)
        [os.unlink(os.path.join(stat_path, x)) for x in os.listdir(stat_path)
                if x != 'status' and x != 'slast' and x != 'slist']

    def exposed_volume_reset(self, host, name):
        self.__volume_reset(host, name)

    def exposed_volume_negotiation(self, host, name, vol_name, chknum):
        self.__volume_set_chknum(host, name, chknum)
        self.__volume_set_volume(host, name, vol_name)
        slast = self.__volume_get_slast(host, name)

        if not self.__volume_exists(vol_name):
            self.__volume_reset(host, name)
            self.__volume_create(vol_name)
            self._dinfo("volume %s %s set status to '%s'" %(host, name, 'all'))
            self.__volume_set_status(host, name, "all")
            return {'sync':"all", 'slast':slast}

        status = self.__volume_get_status(host, name)
        if status is None:
            raise Exp(errno.EEXIST, "%s already exists" % (vol_name))
        elif status == 'inc':
            self.__volume_snapshot_prep(host, name, vol_name)

        return {'sync':status, 'slast':slast}

    def exposed_volume_finish(self, host, name, vol_name, neg):
        for i in range(0, neg['chknum']):
            status = self.__chunk_get_status(host, name, i)
            if status != 'finish':
                raise Exp(errno.EPERM, "%s %s chunk %s not finish" % (host, name, i))

        self.__chunk_reset(host, name)
        if neg['sync'] == "all":
            self._dinfo("volume %s %s set status to '%s'" %(host, name, 'inc'))
            self.__volume_set_status(host, name, "inc")
            self.__volume_set_slist(host, name, [])
        else:
            self.__volume_snapshot_finish(host, name, vol_name)
        self._dinfo("volume %s %s set slast '%s'" %(host, name, neg['slist'][-1]))
        self.__volume_set_slast(host, name, neg['slist'][-1])

    def exposed_volume_left(self, host, name, neg):
        count = 0
        for i in range(0, neg['chknum']):
            status = self.__chunk_get_status(host, name, i)
            if status != 'finish':
                count += 1
        return count

    def exposed_chunk_negotiation(self, host, name, i, l):
        for x in range(i, i + l):
            status = self.__chunk_get_status(host, name, x)
            if status is None:
                return True
            elif status != 'finish':
                return True

        return False

    def exposed_chunk_sync(self, host, name, vol_name, i, l, chunk):
        need = False
        for x in range(i, i + l):
            status = self.__chunk_get_status(host, name, x)
            if status is None:
                need = True
                self.__chunk_set_status(host, name, i, "begin")
            elif status != 'finish':
                need = True

        if not need:
            return

        if chunk:
            self.__chunk_write(host, name, vol_name, i, chunk)

        for x in range(i, i + l):
            self.__chunk_set_status(host, name, x, "finish")

